﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;

namespace Twitter.Tests
{
	public class TestRequestContext : IDisposable
	{
		// All registered responses for the current thread. 
		[ThreadStatic]
		static Dictionary<Uri, UriEntry> uriEntries =
			new Dictionary<Uri, UriEntry>();

		class UriEntry
		{
			public Action<StreamWriter> ResponseAction;
			public MemoryStream RequestStream;
		}

		/// <summary>
		/// Adds the given <paramref name="responseContent"/> as the 
		/// payload of the response retrieved from the given uri.
		/// </summary>
		public void AddResponse(Uri requestUri, string responseContent)
		{
			// Add the response to our internal list.
			AddResponse(requestUri,
				writer =>
				{
					writer.Write(responseContent);
					writer.Flush();
				});
		}

		/// <summary>
		/// Adds a response by passing an action that will write to the 
		/// underlying stream (via a stream writer) that the client will get when it retrieves 
		/// the response stream.
		/// </summary>
		public void AddResponse(Uri requestUri, Action<StreamWriter> responseAction)
		{
			// If already registered, it will be ignored.
			WebRequest.RegisterPrefix(requestUri.Scheme, new TestRequestFactory());

			// Add the response to our internal list.
			uriEntries.Add(requestUri,
				new UriEntry
				{
					ResponseAction = responseAction,
					RequestStream = new MemoryStream()
				}
			);
		}

		public Stream GetRequest(Uri requestUri)
		{
			if (!uriEntries.ContainsKey(requestUri))
			{
				throw new ArgumentException(
					"There is no entry registered for uri " + requestUri.ToString());
			}

			MemoryStream stream = uriEntries[requestUri].RequestStream;
			stream.Position = 0;
			return new NonCloseSteam(stream);
		}

		public void Dispose()
		{
			uriEntries.Clear();
		}

		class TestRequestFactory : IWebRequestCreate
		{
			WebRequest IWebRequestCreate.Create(Uri uri)
			{
				if (!TestRequestContext.uriEntries.ContainsKey(uri))
				{
					throw new ArgumentException(
						"There is no entry registered for uri " + uri.ToString());
				}

				return new TestWebRequest(
					TestRequestContext.uriEntries[uri].ResponseAction, 
					uri);
			}

			class TestWebRequest : WebRequest
			{
				Action<StreamWriter> response;
				Uri requestUri;

				public TestWebRequest(Action<StreamWriter> response, Uri requestUri)
				{
					this.requestUri = requestUri;
					this.response = response;
				}

				public override WebResponse GetResponse()
				{
					return new TestWebResponse(response);
				}

				public override ICredentials Credentials { get; set; }

				public override string ContentType { get; set; }

				public override string Method { get; set; }

				public override long ContentLength { get; set; }

				public override Uri RequestUri
				{
					get { return requestUri; }
				}

				public override Stream GetRequestStream()
				{
					return new NonCloseSteam(
						TestRequestContext.uriEntries[this.RequestUri].RequestStream);
				}
			}

			class TestWebResponse : WebResponse
			{
				Action<StreamWriter> response;

				public TestWebResponse(Action<StreamWriter> response)
				{
					this.response = response;
				}

				public override Stream GetResponseStream()
				{
					MemoryStream mem = new MemoryStream();
					StreamWriter writer = new StreamWriter(mem);
					response(writer);
					writer.Flush();
					mem.Position = 0;
					return mem;
				}

				public override long ContentLength
				{
					get
					{
						MemoryStream mem = new MemoryStream();
						StreamWriter writer = new StreamWriter(mem);
						response(writer);
						writer.Flush();
						mem.Position = 0;
						return mem.Length;
					}
					set { }
				}
			}
		}

		class NonCloseSteam : Stream
		{
			MemoryStream innerStream;

			public NonCloseSteam(MemoryStream baseStream)
			{
				this.innerStream = baseStream;
			}

			public override bool CanRead
			{
				get { return innerStream.CanRead; }
			}

			public override bool CanSeek
			{
				get { return innerStream.CanSeek; }
			}

			public override bool CanWrite
			{
				get { return innerStream.CanWrite; }
			}

			public override void Flush()
			{
				innerStream.Flush();
			}

			public override long Length
			{
				get { return innerStream.Length; }
			}

			public override long Position
			{
				get
				{
					return innerStream.Position;
				}
				set
				{
					innerStream.Position = value;
				}
			}

			public override int Read(byte[] buffer, int offset, int count)
			{
				return innerStream.Read(buffer, offset, count);
			}

			public override long Seek(long offset, SeekOrigin origin)
			{
				return innerStream.Seek(offset, origin);
			}

			public override void SetLength(long value)
			{
				innerStream.SetLength(value);
			}

			public override void Write(byte[] buffer, int offset, int count)
			{
				innerStream.Write(buffer, offset, count);
			}
		}
	}
}
